package model

import (
	"encoding/json"
	"fmt"

	"github.com/invopop/jsonschema"

	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/bsontype"
)

type (
	VolumeName string
	Volumes    map[VolumeName]*string
)

const (
	volumeNameRegex = "^[a-zA-Z0-9._-]+$"
)

func (Volumes) JSONSchema() *jsonschema.Schema {
	return &jsonschema.Schema{
		PatternProperties: map[string]*jsonschema.Schema{volumeNameRegex: {
			AnyOf: []*jsonschema.Schema{
				{Type: "object"},
				{Type: "null"}},
		}},
		AdditionalProperties: jsonschema.FalseSchema,
	}
}

func (d *Volumes) UnmarshalJSON(data []byte) error {
	if string(data) == "null" {
		return nil
	}
	object := map[string]string{}
	if err := json.Unmarshal(data, &object); err != nil {
		return err
	}
	*d = make(map[VolumeName]*string, len(object))
	for k, v := range object {
		if v != "null" {
			(*d)[VolumeName(k)] = &v
		} else {
			(*d)[VolumeName(k)] = nil
		}
	}
	return nil
}

func (d *Volumes) MarshalJSON() ([]byte, error) {
	if d == nil {
		return []byte("null"), nil
	}
	if len(*d) == 0 {
		return []byte("null"), nil
	}
	volumeMap := make(map[VolumeName]string, len(*d))
	for k, v := range *d {
		if v != nil {
			volumeMap[k] = *v
		} else {
			volumeMap[k] = "null"
		}
	}
	return json.Marshal(volumeMap)
}

func (d *Volumes) MarshalBSONValue() (bsontype.Type, []byte, error) {
	if d == nil {
		return bsontype.Null, []byte{}, nil
	}
	if len(*d) == 0 {
		return bsontype.Null, []byte{}, nil
	}
	return bson.MarshalValue(*d)
}

func (d *Volumes) UnmarshalBSONValue(t bsontype.Type, data []byte) error {
	value := bson.RawValue{Type: t, Value: data}
	if t == bsontype.Null {
		return nil
	}
	var bvalue map[string]string

	if err := value.Unmarshal(&bvalue); err != nil {
		return fmt.Errorf("failed to convert environment: %w", err)
	}

	*d = make(map[VolumeName]*string, len(bvalue))
	for key, v := range bvalue {
		if v == string([]byte{}) {
			(*d)[VolumeName(key)] = nil
			continue
		}
		if v != "null" {
			(*d)[VolumeName(key)] = &v
		} else {
			(*d)[VolumeName(key)] = nil
		}
	}

	return nil
}
